package org.microframework.cloud.files.service;

import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.tika.Tika;
import org.microframework.cloud.files.config.FileServiceProperties;
import org.microframework.cloud.files.config.MinioProperties;
import org.microframework.cloud.files.exception.FileOperationException;
import org.microframework.cloud.files.model.FileInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * MinIO文件服务实现
 */
@Slf4j
@Service
public class MinioFileService {

    private final MinioClient minioClient;
    private final MinioProperties minioProperties;
    private final FileServiceProperties fileServiceProperties;
    private final Tika tika = new Tika();

    @Autowired
    public MinioFileService(MinioClient minioClient, MinioProperties minioProperties, 
                          FileServiceProperties fileServiceProperties) {
        this.minioClient = minioClient;
        this.minioProperties = minioProperties;
        this.fileServiceProperties = fileServiceProperties;
    }

    /**
     * 上传文件
     * @param file 文件
     * @param isPublic 是否公开访问
     * @return 文件信息
     */
    public FileInfo uploadFile(MultipartFile file, boolean isPublic) {
        return uploadFile(file, isPublic, null);
    }

    /**
     * 上传文件到指定目录
     * @param file 文件
     * @param isPublic 是否公开访问
     * @param directory 目录
     * @return 文件信息
     */
    public FileInfo uploadFile(MultipartFile file, boolean isPublic, String directory) {
        try {
            // 校验文件类型
            validateFileType(file);
            
            // 准备文件元数据
            String originalFilename = file.getOriginalFilename();
            String extension = getFileExtension(originalFilename);
            String contentType = file.getContentType();
            
            // 检测文件实际内容类型（安全验证）
            try (InputStream is = file.getInputStream()) {
                String detectedContentType = tika.detect(is);
                if (!detectedContentType.equals(contentType)) {
                    log.warn("文件内容类型({})与声明类型({})不匹配", detectedContentType, contentType);
                    contentType = detectedContentType;
                }
            }
            
            // 生成文件名
            String fileId = UUID.randomUUID().toString().replace("-", "");
            String path = generatePath(directory);
            String objectName = path + fileId + (StringUtils.hasText(extension) ? "." + extension : "");
            
            // 选择存储桶
            String bucketName = isPublic 
                ? minioProperties.getBucket().getPublic() 
                : minioProperties.getBucket().getDefault();
            
            // 准备文件元数据
            Map<String, String> metadata = new HashMap<>();
            metadata.put("Content-Type", contentType);
            metadata.put("Original-Filename", originalFilename);
            metadata.put("File-Id", fileId);
            metadata.put("Upload-Time", LocalDateTime.now().toString());
            
            // 计算文件MD5
            String md5;
            try (InputStream is = file.getInputStream()) {
                md5 = DigestUtils.md5DigestAsHex(is);
                metadata.put("MD5", md5);
            }
            
            // 上传文件
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .contentType(contentType)
                    .userMetadata(metadata)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .build());
            
            // 生成文件访问URL
            String url;
            if (isPublic) {
                url = fileServiceProperties.getPublicUrlPrefix() + objectName;
            } else {
                url = getPresignedUrl(bucketName, objectName, fileServiceProperties.getExpiryHours());
            }
            
            // 生成缩略图（如果是图片且启用了缩略图）
            String thumbnailUrl = null;
            if (isImageFile(contentType) && fileServiceProperties.getPreview().getImage().getThumbnail().isEnabled()) {
                thumbnailUrl = generateThumbnail(file, objectName, bucketName, isPublic);
            }
            
            // 构建并返回文件信息
            return FileInfo.builder()
                    .fileId(fileId)
                    .originalFilename(originalFilename)
                    .objectName(objectName)
                    .path(path)
                    .size(file.getSize())
                    .contentType(contentType)
                    .extension(extension)
                    .url(url)
                    .thumbnailUrl(thumbnailUrl)
                    .bucketName(bucketName)
                    .isPublic(isPublic)
                    .uploadTime(LocalDateTime.now())
                    .expireTime(isPublic ? null : LocalDateTime.now().plusHours(fileServiceProperties.getExpiryHours()))
                    .md5(md5)
                    .build();
            
        } catch (Exception e) {
            log.error("文件上传失败", e);
            throw new FileOperationException("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 根据文件ID下载文件
     * @param fileId 文件ID
     * @return 文件字节数组
     */
    public byte[] downloadFile(String fileId) {
        try {
            // 查找文件信息
            FileInfo fileInfo = getFileInfoById(fileId);
            if (fileInfo == null) {
                throw new FileOperationException("文件不存在: " + fileId);
            }
            
            // 获取文件对象
            GetObjectResponse response = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(fileInfo.getBucketName())
                    .object(fileInfo.getObjectName())
                    .build());
            
            // 读取文件内容
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[16384];
            int bytesRead;
            while ((bytesRead = response.read(buffer, 0, buffer.length)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            
            return outputStream.toByteArray();
            
        } catch (Exception e) {
            log.error("文件下载失败: {}", fileId, e);
            throw new FileOperationException("文件下载失败: " + e.getMessage(), e);
        }
    }

    /**
     * 删除文件
     * @param fileId 文件ID
     * @return 是否删除成功
     */
    public boolean deleteFile(String fileId) {
        try {
            // 查找文件信息
            FileInfo fileInfo = getFileInfoById(fileId);
            if (fileInfo == null) {
                return false;
            }
            
            // 删除文件
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(fileInfo.getBucketName())
                    .object(fileInfo.getObjectName())
                    .build());
            
            // 如果有缩略图，也一并删除
            if (StringUtils.hasText(fileInfo.getThumbnailUrl())) {
                String thumbnailObjectName = fileInfo.getObjectName().replace(".", "_thumbnail.");
                try {
                    minioClient.removeObject(RemoveObjectArgs.builder()
                            .bucket(fileInfo.getBucketName())
                            .object(thumbnailObjectName)
                            .build());
                } catch (Exception e) {
                    log.warn("删除缩略图失败: {}", thumbnailObjectName, e);
                }
            }
            
            return true;
            
        } catch (Exception e) {
            log.error("删除文件失败: {}", fileId, e);
            throw new FileOperationException("删除文件失败: " + e.getMessage(), e);
        }
    }

    /**
     * 批量删除文件
     * @param fileIds 文件ID集合
     * @return 删除失败的文件ID列表
     */
    public List<String> batchDeleteFiles(List<String> fileIds) {
        if (fileIds == null || fileIds.isEmpty()) {
            return Collections.emptyList();
        }
        
        List<String> failedFiles = new ArrayList<>();
        Map<String, List<DeleteObject>> bucketObjectsMap = new HashMap<>();
        
        try {
            // 根据存储桶分组要删除的对象
            for (String fileId : fileIds) {
                try {
                    FileInfo fileInfo = getFileInfoById(fileId);
                    if (fileInfo == null) {
                        failedFiles.add(fileId);
                        continue;
                    }
                    
                    String bucketName = fileInfo.getBucketName();
                    String objectName = fileInfo.getObjectName();
                    
                    // 将对象添加到对应存储桶的删除列表
                    bucketObjectsMap.computeIfAbsent(bucketName, k -> new ArrayList<>())
                            .add(new DeleteObject(objectName));
                    
                    // 如果有缩略图，也添加到删除列表
                    if (StringUtils.hasText(fileInfo.getThumbnailUrl())) {
                        String thumbnailObjectName = objectName.replace(".", "_thumbnail.");
                        bucketObjectsMap.get(bucketName).add(new DeleteObject(thumbnailObjectName));
                    }
                } catch (Exception e) {
                    log.error("准备删除文件时出错: {}", fileId, e);
                    failedFiles.add(fileId);
                }
            }
            
            // 对每个存储桶执行批量删除
            for (Map.Entry<String, List<DeleteObject>> entry : bucketObjectsMap.entrySet()) {
                String bucketName = entry.getKey();
                List<DeleteObject> objects = entry.getValue();
                
                Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
                        .bucket(bucketName)
                        .objects(objects)
                        .build());
                
                // 收集删除失败的对象
                for (Result<DeleteError> result : results) {
                    DeleteError error = result.get();
                    log.error("删除对象失败: {}, 错误: {}", error.objectName(), error.message());
                    failedFiles.add(error.objectName());
                }
            }
            
            return failedFiles;
            
        } catch (Exception e) {
            log.error("批量删除文件失败", e);
            throw new FileOperationException("批量删除文件失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取文件详细信息
     * @param fileId 文件ID
     * @return 文件信息
     */
    public FileInfo getFileInfoById(String fileId) {
        // 在实际应用中，文件ID到对象名称的映射应该存储在数据库中
        // 这里简化处理，直接在默认和公共存储桶中查找包含对应fileId的对象
        
        try {
            // 在默认存储桶中查找
            FileInfo fileInfo = findFileInBucket(minioProperties.getBucket().getDefault(), fileId);
            if (fileInfo != null) {
                return fileInfo;
            }
            
            // 在公共存储桶中查找
            return findFileInBucket(minioProperties.getBucket().getPublic(), fileId);
            
        } catch (Exception e) {
            log.error("获取文件信息失败: {}", fileId, e);
            throw new FileOperationException("获取文件信息失败: " + e.getMessage(), e);
        }
    }

    /**
     * 获取预签名URL
     * @param fileId 文件ID
     * @param expiryHours 过期时间（小时）
     * @return 预签名URL
     */
    public String getPresignedUrlByFileId(String fileId, Integer expiryHours) {
        try {
            FileInfo fileInfo = getFileInfoById(fileId);
            if (fileInfo == null) {
                throw new FileOperationException("文件不存在: " + fileId);
            }
            
            if (fileInfo.getIsPublic()) {
                return fileInfo.getUrl(); // 公共文件直接返回URL
            }
            
            int expiry = expiryHours != null ? expiryHours : fileServiceProperties.getExpiryHours();
            return getPresignedUrl(fileInfo.getBucketName(), fileInfo.getObjectName(), expiry);
            
        } catch (Exception e) {
            log.error("获取预签名URL失败: {}", fileId, e);
            throw new FileOperationException("获取预签名URL失败: " + e.getMessage(), e);
        }
    }

    /**
     * 列出指定目录下的文件
     * @param directory 目录路径
     * @param isPublic 是否查询公共存储桶
     * @return 文件信息列表
     */
    public List<FileInfo> listFiles(String directory, boolean isPublic) {
        try {
            String bucketName = isPublic 
                ? minioProperties.getBucket().getPublic() 
                : minioProperties.getBucket().getDefault();
            
            String prefix = StringUtils.hasText(directory) ? directory : "";
            if (StringUtils.hasText(prefix) && !prefix.endsWith("/")) {
                prefix += "/";
            }
            
            Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                    .bucket(bucketName)
                    .prefix(prefix)
                    .recursive(true)
                    .build());
            
            return StreamSupport.stream(results.spliterator(), false)
                    .map(result -> {
                        try {
                            Item item = result.get();
                            // 跳过目录项和缩略图
                            if (item.isDir() || item.objectName().contains("_thumbnail.")) {
                                return null;
                            }
                            
                            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
                                    .bucket(bucketName)
                                    .object(item.objectName())
                                    .build());
                            
                            Map<String, String> metadata = stat.userMetadata();
                            String fileId = metadata.getOrDefault("File-Id", "");
                            String originalFilename = metadata.getOrDefault("Original-Filename", item.objectName());
                            
                            String url = isPublic 
                                ? fileServiceProperties.getPublicUrlPrefix() + item.objectName()
                                : getPresignedUrl(bucketName, item.objectName(), fileServiceProperties.getExpiryHours());
                            
                            // 检查是否有缩略图
                            String thumbnailUrl = null;
                            String thumbnailObjectName = item.objectName().replace(".", "_thumbnail.");
                            try {
                                minioClient.statObject(StatObjectArgs.builder()
                                        .bucket(bucketName)
                                        .object(thumbnailObjectName)
                                        .build());
                                thumbnailUrl = isPublic 
                                    ? fileServiceProperties.getPublicUrlPrefix() + thumbnailObjectName
                                    : getPresignedUrl(bucketName, thumbnailObjectName, fileServiceProperties.getExpiryHours());
                            } catch (Exception e) {
                                // 缩略图不存在，忽略
                            }
                            
                            String path = item.objectName().substring(0, item.objectName().lastIndexOf('/') + 1);
                            String extension = getFileExtension(item.objectName());
                            
                            return FileInfo.builder()
                                    .fileId(fileId)
                                    .originalFilename(originalFilename)
                                    .objectName(item.objectName())
                                    .path(path)
                                    .size(item.size())
                                    .contentType(stat.contentType())
                                    .extension(extension)
                                    .url(url)
                                    .thumbnailUrl(thumbnailUrl)
                                    .bucketName(bucketName)
                                    .isPublic(isPublic)
                                    .uploadTime(LocalDateTime.ofInstant(item.lastModified().toInstant(), ZoneId.systemDefault()))
                                    .expireTime(isPublic ? null : LocalDateTime.now().plusHours(fileServiceProperties.getExpiryHours()))
                                    .md5(metadata.getOrDefault("MD5", ""))
                                    .build();
                        } catch (Exception e) {
                            log.error("获取文件信息失败", e);
                            return null;
                        }
                    })
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());
            
        } catch (Exception e) {
            log.error("列出文件失败", e);
            throw new FileOperationException("列出文件失败: " + e.getMessage(), e);
        }
    }

    /**
     * 在指定存储桶中查找文件
     */
    private FileInfo findFileInBucket(String bucketName, String fileId) throws Exception {
        Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
                .bucket(bucketName)
                .recursive(true)
                .build());
        
        for (Result<Item> result : results) {
            Item item = result.get();
            if (item.isDir() || item.objectName().contains("_thumbnail.")) {
                continue;
            }
            
            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(item.objectName())
                    .build());
            
            Map<String, String> metadata = stat.userMetadata();
            String objectFileId = metadata.getOrDefault("File-Id", "");
            
            if (fileId.equals(objectFileId)) {
                String originalFilename = metadata.getOrDefault("Original-Filename", item.objectName());
                boolean isPublic = bucketName.equals(minioProperties.getBucket().getPublic());
                
                String url = isPublic 
                    ? fileServiceProperties.getPublicUrlPrefix() + item.objectName()
                    : getPresignedUrl(bucketName, item.objectName(), fileServiceProperties.getExpiryHours());
                
                // 检查是否有缩略图
                String thumbnailUrl = null;
                String thumbnailObjectName = item.objectName().replace(".", "_thumbnail.");
                try {
                    minioClient.statObject(StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(thumbnailObjectName)
                            .build());
                    thumbnailUrl = isPublic 
                        ? fileServiceProperties.getPublicUrlPrefix() + thumbnailObjectName
                        : getPresignedUrl(bucketName, thumbnailObjectName, fileServiceProperties.getExpiryHours());
                } catch (Exception e) {
                    // 缩略图不存在，忽略
                }
                
                String path = item.objectName().substring(0, item.objectName().lastIndexOf('/') + 1);
                String extension = getFileExtension(item.objectName());
                
                return FileInfo.builder()
                        .fileId(fileId)
                        .originalFilename(originalFilename)
                        .objectName(item.objectName())
                        .path(path)
                        .size(item.size())
                        .contentType(stat.contentType())
                        .extension(extension)
                        .url(url)
                        .thumbnailUrl(thumbnailUrl)
                        .bucketName(bucketName)
                        .isPublic(isPublic)
                        .uploadTime(LocalDateTime.ofInstant(item.lastModified().toInstant(), ZoneId.systemDefault()))
                        .expireTime(isPublic ? null : LocalDateTime.now().plusHours(fileServiceProperties.getExpiryHours()))
                        .md5(metadata.getOrDefault("MD5", ""))
                        .build();
            }
        }
        
        return null;
    }

    /**
     * 获取预签名URL
     */
    private String getPresignedUrl(String bucketName, String objectName, int expiryHours) throws Exception {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .bucket(bucketName)
                .object(objectName)
                .expiry(expiryHours, TimeUnit.HOURS)
                .build());
    }

    /**
     * 生成存储路径
     */
    private String generatePath(String directory) {
        StringBuilder pathBuilder = new StringBuilder();
        
        // 添加可选的自定义目录
        if (StringUtils.hasText(directory)) {
            if (!directory.endsWith("/")) {
                directory += "/";
            }
            pathBuilder.append(directory);
        }
        
        // 添加日期目录结构：年/月/日/
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd/");
        pathBuilder.append(LocalDateTime.now().format(formatter));
        
        return pathBuilder.toString();
    }

    /**
     * 获取文件扩展名
     */
    private String getFileExtension(String filename) {
        if (filename == null || filename.lastIndexOf(".") == -1) {
            return "";
        }
        return filename.substring(filename.lastIndexOf(".") + 1).toLowerCase();
    }

    /**
     * 判断是否为图片文件
     */
    private boolean isImageFile(String contentType) {
        return contentType != null && contentType.startsWith("image/");
    }

    /**
     * 验证文件类型
     */
    private void validateFileType(MultipartFile file) {
        String contentType = file.getContentType();
        
        if (contentType == null) {
            throw new FileOperationException("未知的文件类型");
        }
        
        List<String> allowedTypes = fileServiceProperties.getAllowedContentTypes();
        if (!allowedTypes.isEmpty() && !allowedTypes.contains(contentType)) {
            throw new FileOperationException("不支持的文件类型: " + contentType);
        }
    }

    /**
     * 生成缩略图
     */
    private String generateThumbnail(MultipartFile file, String objectName, String bucketName, boolean isPublic)
            throws Exception {
        // 获取缩略图配置
        FileServiceProperties.Preview.Image.Thumbnail thumbnailConfig = 
                fileServiceProperties.getPreview().getImage().getThumbnail();
        
        // 生成缩略图
        try (InputStream inputStream = file.getInputStream()) {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            
            Thumbnails.of(inputStream)
                    .size(thumbnailConfig.getWidth(), thumbnailConfig.getHeight())
                    .outputQuality(thumbnailConfig.getQuality())
                    .outputFormat(getFileExtension(file.getOriginalFilename()))
                    .toOutputStream(outputStream);
            
            // 缩略图对象名称
            String thumbnailObjectName = objectName.replace(".", "_thumbnail.");
            
            // 上传缩略图
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(thumbnailObjectName)
                    .contentType(file.getContentType())
                    .stream(new ByteArrayInputStream(outputStream.toByteArray()), outputStream.size(), -1)
                    .build());
            
            // 返回缩略图URL
            if (isPublic) {
                return fileServiceProperties.getPublicUrlPrefix() + thumbnailObjectName;
            } else {
                return getPresignedUrl(bucketName, thumbnailObjectName, fileServiceProperties.getExpiryHours());
            }
        }
    }
} 